package com.mas.admin.biz.service.permission;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import com.mas.admin.api.permission.dto.DeptDataPermissionRespDTO;
import com.mas.admin.biz.dal.dataobject.permission.MenuDO;
import com.mas.admin.biz.dal.dataobject.permission.RoleDO;
import com.mas.admin.biz.dal.dataobject.permission.RoleMenuDO;
import com.mas.admin.biz.dal.dataobject.permission.UserRoleDO;
import com.mas.admin.biz.dal.mysql.permission.RoleMenuMapper;
import com.mas.admin.biz.dal.mysql.permission.UserRoleMapper;
import com.mas.framework.common.core.enums.CommonStatusEnum;
import com.mas.framework.common.util.collection.CollectionUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;

import static java.util.Collections.singleton;

/**
 * @author godyao
 * @description 权限实现类
 * @date 2023年02月01日21:09
 */
@Service
@Slf4j
public class PermissionServiceImpl implements PermissionService{

    @Resource
    private RoleMenuMapper roleMenuMapper;

    @Resource
    private UserRoleMapper userRoleMapper;

    @Resource
    private MenuService menuService;

    @Resource
    private RoleService roleService;

    /**
     * 定时执行 {@link #schedulePeriodicRefresh()} 的周期
     * 因为已经通过 Redis Pub/Sub 机制，所以频率不需要高
     */
    private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;

    /**
     * 角色编号与菜单编号的缓存映射
     * key：角色编号
     * value：菜单编号的数组
     * 这里声明 volatile 修饰的原因是，每次刷新时，直接修改指向
     */
    @Getter
    private volatile MultiValueMap<Long, Long> roleMenuCache;

    /**
     * 菜单编号与角色编号的缓存映射
     * key：菜单编号
     * value：角色编号的数组
     */
    @Getter
    private volatile MultiValueMap<Long, Long> menuRoleCache;

    /**
     * 缓存 RoleMenu 的最大更新时间，用于后续的增量轮询，判断是否有更新
     */
    @Getter
    private volatile LocalDateTime roleMenuMaxUpdateTime;

    /**
     * 用户编号与角色编号的缓存映射
     * key：用户编号
     * value：角色编号的数组
     * 这里声明 volatile 修饰的原因是，每次刷新时，直接修改指向
     */
    @Getter
    private volatile Map<Long, Set<Long>> userRoleCache;

    /**
     * 缓存 UserRole 的最大更新时间，用于后续的增量轮询，判断是否有更新
     */
    @Getter
    private volatile LocalDateTime userRoleMaxUpdateTime;


    @Override
    @PostConstruct
    public void initLocalCache() {
        initLocalCacheIfUpdateForRoleMenu(null);
        initLocalCacheIfUpdateForUserRole(null);
    }

    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
    public void schedulePeriodicRefresh() {
        initLocalCacheIfUpdateForRoleMenu(this.roleMenuMaxUpdateTime);
        initLocalCacheIfUpdateForUserRole(this.userRoleMaxUpdateTime);
    }

    /**
     * 刷新 UserRole 本地缓存
     * @param maxUpdateTime 最大更新时间
     *                      1. 如果 maxUpdateTime 为 null，则“强制”刷新缓存
     *                      2. 如果 maxUpdateTime 不为 null，判断自 maxUpdateTime 是否有数据发生变化，有的情况下才刷新缓存
     */
    private void initLocalCacheIfUpdateForUserRole(LocalDateTime maxUpdateTime) {
        // 第一步：基于 maxUpdateTime 判断缓存是否刷新。
        // 如果没有增量的数据变化，则不进行本地缓存的刷新
        if (maxUpdateTime != null
                && userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
            log.info("[initLocalCacheIfUpdateForRoleMenu][数据未发生变化({}), 本地缓存不更新]", maxUpdateTime);
            return;
        }
        final List<UserRoleDO> userRoles = userRoleMapper.selectList();
        log.info("[initLocalCacheIfUpdateForUserRole][缓存用户与角色，数量为:{}]", userRoles.size());

        // 第二步：构建缓存。
        userRoleCache = new HashMap<>(userRoles.size());
        final MultiValueMap<Long, Long> userRoleMap = new LinkedMultiValueMap<>();
        userRoles.forEach(userRoleDO -> {
            userRoleMap.add(userRoleDO.getRoleId(), userRoleDO.getRoleId());
        });
        userRoleMap.forEach((userId, roleIds) -> {
            userRoleCache.put(userId, new HashSet<>(roleIds));
        });
        // 第三步：设置最新的 maxUpdateTime，用于下次的增量判断。
        this.userRoleMaxUpdateTime = CollectionUtils.getMaxValue(userRoles, UserRoleDO::getUpdateTime);
    }

    /**
     * 刷新 RoleMenu 本地缓存
     *
     * @param maxUpdateTime 最大更新时间
     *                      1. 如果 maxUpdateTime 为 null，则“强制”刷新缓存
     *                      2. 如果 maxUpdateTime 不为 null，判断自 maxUpdateTime 是否有数据发生变化，有的情况下才刷新缓存
     */
    private void initLocalCacheIfUpdateForRoleMenu(LocalDateTime maxUpdateTime) {
        // 第一步：基于 maxUpdateTime 判断缓存是否刷新。
        // 如果没有增量的数据变化，则不进行本地缓存的刷新
        if (maxUpdateTime != null
        && roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
            log.info("[initLocalCacheIfUpdateForRoleMenu][数据未发生变化({}), 本地缓存不更新]", maxUpdateTime);
            return;
        }
        final List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
        log.info("[initLocalCacheIfUpdateForRoleMenu][缓存角色与菜单，数量为:{}]", roleMenus.size());

        // 第二步：构建缓存。
        roleMenuCache = new LinkedMultiValueMap<>();
        menuRoleCache = new LinkedMultiValueMap<>();
        roleMenus.forEach(roleMenuDO -> {
            roleMenuCache.add(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
            menuRoleCache.add(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
        });
        // 第三步：设置最新的 maxUpdateTime，用于下次的增量判断。
        this.roleMenuMaxUpdateTime = CollectionUtils.getMaxValue(roleMenus, RoleMenuDO::getUpdateTime);
    }


    @Override
    public List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes, Collection<Integer> menusStatuses) {
        return null;
    }

    @Override
    public Set<Long> getUserRoleIdsFromCache(Long userId, Collection<Integer> roleStatuses) {
        final Set<Long> cacheRoleIds = userRoleCache.get(userId);
        // 创建用户的时候没有分配角色，会存在空指针异常
        if (CollUtil.isEmpty(cacheRoleIds)) {
            return Collections.emptySet();
        }
        // 去重
        Set<Long> roleIds = new HashSet<>(cacheRoleIds);
        // 过滤角色状态
        if (CollectionUtil.isNotEmpty(roleStatuses)) {
            roleIds.removeIf(roleId -> {
                final RoleDO roleFromCache = roleService.getRoleFromCache(roleId);
                return Objects.isNull(roleFromCache) || !roleStatuses.contains(roleFromCache.getStatus());
            });
        }
        return roleIds;
    }

    @Override
    public Set<Long> getRoleMenuIds(Long roleId) {
        return null;
    }

    @Override
    public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
        return null;
    }

    @Override
    public void assignRoleMenu(Long roleId, Set<Long> menuIds) {

    }

    @Override
    public Set<Long> getUserRoleIdListByUserId(Long userId) {
        return null;
    }

    @Override
    public void assignUserRole(Long userId, Set<Long> roleIds) {

    }

    @Override
    public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {

    }

    @Override
    public void processRoleDeleted(Long roleId) {

    }

    @Override
    public void processMenuDeleted(Long menuId) {

    }

    @Override
    public void processUserDeleted(Long userId) {

    }

    @Override
    public boolean hasAnyPermissions(Long userId, String... permissions) {
        if (ArrayUtil.isEmpty(permissions)) {
            // 无需权限
            return true;
        }
        // 获得当前登录的角色。如果为空，说明没有权限
        final Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
        if (CollUtil.isEmpty(roleIds)) {
            return false;
        }
        // 判断是否为超管
        if (roleService.hasAnySuperAdmin(roleIds)) {
            return true;
        }
        // 遍历权限，判断是否有一个满足
        return Arrays.stream(permissions)
                .anyMatch(permission -> {
                    // 一个 permission 可能对应多个 menu
                    List<MenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission);
                    // 采用严格模式，如果权限找不到对应的 Menu 的话，认为无权限
                    if (CollUtil.isEmpty(menuList)) {
                        return false;
                    }
                    // 判断是否有一个拥有指定权限的角色
                    return menuList.stream()
                            .anyMatch(menuDO ->
                                CollUtil.containsAny(roleIds, menuRoleCache.get(menuDO.getId()))
                            );
                });
    }

    @Override
    public boolean hasAnyRoles(Long userId, String... roles) {
        if (ArrayUtil.isEmpty(roles)) {
            return true;
        }
        // 获得当前登录的角色。如果为空，说明没有权限
        final Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
        if (CollUtil.isEmpty(roleIds)) {
            return false;
        }
        if (roleService.hasAnySuperAdmin(roleIds)) {
            return true;
        }
        Set<String> roleCodes = CollectionUtils.convertSet(roleService.getRolesFromCache(roleIds), RoleDO::getCode);
        return CollUtil.containsAny(roleCodes, new HashSet<>(Arrays.asList(roles)));
    }

    @Override
    public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
        return null;
    }
}
